// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "command_handler.h"

#include <ctype.h>  // isdigit()
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <iostream>
#include <string>
#include <sstream>
#include <map>

#include "general_installer.h"
#include "diskimage_installer.h"

using std::cerr;
using std::endl;
using std::string;
using std::stringstream;

namespace chromeos_memento_updater {

const string CommandHandler::kOptionList[] = {
  "reset_device",
  "partition",
  "factory",
  "release",
  "stateful",
  "oem",
  "efi",
  "firmware",
  "activate"
};
const int CommandHandler::kOptions = 9;

CommandHandler::CommandHandler(int argc, char** argv) {
  installer_ptr_ = NULL;
  have_release_ = false;
  have_factory_ = false;
  SetRootDev();
  ParseCommandLine(argc, argv);
}

CommandHandler::~CommandHandler() {
  if (installer_ptr_ != NULL) {
    delete installer_ptr_;
  }
}

void CommandHandler::Usage() {
  cerr << "memento_update --to <dest-device>" << endl <<
          "               --from <type>:<location>" << endl <<
          "               --contents <content:option, ...>" << endl <<
          "               --board <board>" << endl;
  cerr << endl;
  cerr << "Please check README for more information about Usage." << endl;
  exit(0);
}

void CommandHandler::SetRootDev() {
  FILE* fp = popen("rootdev -s", "r");
  if (fp == NULL) {
    cerr << "ERROR: Cannot run rootdev." << endl;
    exit(1);
  }
  int kDevLength = 100;
  char buffer[kDevLength];
  buffer[0] = '\0';
  fgets(buffer, kDevLength, fp);
  if (buffer[0] == '\0') {
    // Empty string
    cerr << "ERROR: Run rootdev failed" << endl;
    exit(1);
  }
  root_device_.assign(buffer);
}

string CommandHandler::GetOutputDevicePartition(PartitionNum partition) {
  stringstream device;
  device << output_device_;
  // Ends with digit, add 'p'
  if (isdigit(output_device_[output_device_.size() - 1])) {
    device << 'p';
  }
  // Check if boot and install on the same device
  if (root_device_.find(output_device_) != string::npos) {
    // It is not possible to boot and install both image on the same device.
    if (have_release_ && have_factory_ ) {
      cerr << "ERROR: Not able to install both release and factory when boot"
              "on output device." << endl;
      exit(1);
    }
    switch (partition) {
      case kFactoryPartition:
      case kReleasePartition:
        if (root_device_.find("3") != string::npos) {
          device << '5';
        } else {
          device << '3';
        }
        break;
      case kFactoryKernelPartition:
      case kReleaseKernelPartition:
        if (root_device_.find("3") != string::npos) {
          device << '4';
        } else {
          device << '2';
        }
        break;
      default:
        device << partition;
    }
  } else {
    device << partition;
  }
  return device.str();
}

void CommandHandler::ParseInputDevice(const string& input_string,
                                      const string& running_dir) {
// TODO(chunyen): find a solution to download from http://chromeos-image
// TODO(chunyen): Fill in the code blocks after the corresponding handler
// are written.
  if (input_string.find("image:") == 0) {
    input_device_ = input_string.substr(strlen("image:"));
    DiskImageInstallFormat format = kImage;
    const string kZipSuffix = ".zip";
    // Check if path ends with .zip
    if (input_device_.find(kZipSuffix) ==
        input_device_.length() - kZipSuffix.length()) {
      format = kZip;
    }
    installer_ptr_ = new DiskImageInstaller(running_dir, board_,
                                            input_device_, format);
// TODO(chunyen):
//  } else if (input_string.find("shopfloor:") == 0) {
//  } else if (input_string.find("miniomaha:") == 0) {
//  } else if (input_string.find("recovery:") == 0){
  } else {
    cerr << "Unrecognized source media." << endl;
    Usage();
  }
}

void CommandHandler::ParseContent(const string& content_string) {
  std::istringstream content(content_string);
  string token;
  // Create a map that map option string to enum
  std::map<string, InstallOption> string_to_enum;
  for (int i = 0; i < kOptions; i++) {
    string_to_enum[kOptionList[i]] = static_cast<InstallOption>(i);
  }
  while (std::getline(content, token, ',')) {
    InstallOption option_id;
    string option_string;
    string key;
    size_t colon;
    if ((colon = token.find(":")) != string::npos) {
      key = token.substr(0, colon);
      option_string.assign(token.substr(colon + 1));
    } else {
      key = token;
    }
    std::map<string, InstallOption>::iterator it = string_to_enum.find(key);
    if (it == string_to_enum.end()) {
      cerr << "Cannot find matching option: " << token << endl;
      Usage();
    } else {
      option_id = it->second;
    }
    if (option_id == kRelease) {
      have_release_ = true;
    }
    if (option_id == kFactory) {
      have_factory_ = true;
    }
    option_list_.push_back(make_pair(option_id, option_string));
  }
}

void CommandHandler::ParseCommandLine(int argc, char** argv) {

  string input_string;
  string content_string;
  int option_index = 0;
  char c;
  static struct option long_options[] = {
    {"to", required_argument, 0, 't'},
    {"from", required_argument, 0, 'f'},
    {"contents", required_argument, 0, 'c'},
    {"board", required_argument, 0, 'b'}
  };
  while ((c = getopt_long(argc, argv, "t:f:c:b:",
                          long_options, &option_index)) != -1) {
    switch(c) {
     case 't':
      output_device_.assign(optarg);
      break;
     case 'f':
      input_string.assign(optarg);
      break;
     case 'c':
      content_string.assign(optarg);
      break;
     case 'b':
      board_.assign(optarg);
      break;
     case '?':
      Usage();
      break;
     default:
      cerr << "Unexpected return value " << c << " from getopt." << endl;
      exit(1);
      break;
    }
  }
  if (output_device_.empty() || input_string.empty() ||
      content_string.empty()) {
    Usage();
  }
  // TODO(chunyen): Auto detect board?
  if (board_.empty()) {
    cerr << "Auto detect board is not supported yet.";
    Usage();
  }
  // Find the running directory from argv[0]
  string program_name(argv[0]);
  string home_dir(".");

  size_t pos;
  if ((pos = program_name.find_last_of('/')) != string::npos) {
    home_dir.assign(program_name.substr(0, program_name.length() - pos));
  }
  cerr << home_dir << endl;
  ParseInputDevice(input_string, home_dir);
  ParseContent(content_string);
}

}  // namespace chromeos_memento_updater

